const M3U8 = require("m3u8-parser")
const runParallel = require("run-parallel-limit");
const fs = require("fs-extra");
const path = require("path");
const axios = require("axios").default.create({
  headers: {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "accept-encoding": "gzip, deflate, br",
    "accept-language": "en-US,en;q=0.9,zh;q=0.8,zh-CN;q=0.7",
    "cookie": "csrfToken=-rVdG08KxNS9-ULJggS-PPUk; webp_supported=%7B%22lossy%22%3Atrue%2C%22lossless%22%3Atrue%2C%22alpha%22%3Atrue%2C%22animation%22%3Atrue%7D; session_id=2559795361D73EBE; _did=web_6586110583CF2F04; Hm_lvt_2af69bc2b378fb58ae04ed2a04257ed1=1591286064; ac__avi=101094053700da7c9806637d61226c3093a0d134934d1005411cf6c2c6febafe3a364146315ef65213; lsv_js_player_v2_main=d84d5d; safety_id=AAHSariAgSzK4aOBCFJEyNLh; cur_req_id=76569089628C7F68_self_03fa678113f4365b41b957a266392e10; cur_group_id=76569089628C7F68_self_03fa678113f4365b41b957a266392e10_0; Hm_lpvt_2af69bc2b378fb58ae04ed2a04257ed1=1591445728",
    "sec-fetch-dest": "document",
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "same-origin",
    "sec-fetch-user": "?1",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4115.5 Safari/537.36 OPR/70.0.3707.0 (Edition developer)"
  }
});
const filenamify = require('filenamify');
const url = require("url");
// const runPara = require("run-parallel-limit");
const process = require("process");
const cp = require("child_process");

const SAVE_DIR = 'G:\\ACFUN_';
const PARTS_DIR = 'G:\ACFUN_\\parts';
/**
 * 
 * @param {String} m3u8_link 
 */
function getM3u8Content(m3u8_link) {
  return new Promise(resolve => axios.get(m3u8_link).then(axresp => resolve(axresp.data)))
}

/**
 * @returns {Promise<{ok:Boolean,msg:String,data:{
 * current_m3u8Link:String,
 * current_filename:String,
 * current_videoId:Number,
 * current_uploadTime:Date,
 * has_how_many_videos:Number,
 * many_videos_title:String
 * }}>}
 * @param {String} acid for example,"ac16006738" 
 */
function getVideoInfoOf(acid) {
  return new Promise(resolve => {
    axios.get(`https://www.acfun.cn/v/${acid}`, {

    }).then(axresp => {
      let htmlstr = axresp.data;
      let match = htmlstr.match(/window\.videoInfo = (.*);/);
      // debugger
      let infos = JSON.parse(match[1]);
      let ksPlayObj = JSON.parse(infos.currentVideoInfo.ksPlayJson);
      // debugger
      return resolve({
        ok: true,
        msg: "ok",
        data: {
          current_m3u8Link: ksPlayObj.adaptationSet.representation[0].url,
          current_filename: infos.currentVideoInfo.fileName || infos.currentVideoInfo.title,
          current_uploadTime: new Date(infos.createTimeMillis),
          current_videoId: Number(infos.currentVideoInfo.id),
          has_how_many_videos: infos.videoList.length,
          many_videos_title: infos.title
        }
      })
      debugger
    }).catch(axerr => {
      debugger
    })
  })
}

/**
 * 
 * @param {String} acid 
 * @param {Number} page
 */
function downloadAllInAcid(acid, page = 1) {
  return new Promise(async resolve => {
    let o_getInfo = await getVideoInfoOf(`${acid}_${page}`);
    let parsed_m3u8url = url.parse(o_getInfo.data.current_m3u8Link);
    var parser = new M3U8.Parser();
    let m3u8Content = await getM3u8Content(o_getInfo.data.current_m3u8Link);
    parser.push(m3u8Content);
    let partsDownloadUris = parser.manifest.segments.map(e => `https://${parsed_m3u8url.hostname}/mediacloud/acfun/acfun_video/hls/${e.uri}`)
    // debugger
    let arr = []
    for (let pt of partsDownloadUris) {
      let o_d = await downloadTsSegment(pt);
      if (!o_d.ok) {
        for (let lf of arr) {
          await fs.unlink(lf);
        }
        console.log(acid, page, "retry...");
        let o_retry = await downloadAllInAcid(acid, page);
        return resolve();
      }
      arr.push(o_d.local_filepath)
    }
    let o_filelist = await createFileList(arr);
    const moment = require("moment-timezone")
    let name_time = `${moment(o_getInfo.data.current_uploadTime).tz("Asia/Shanghai").format('YYYYMMDDHH')}`
    let _filename = filenamify(o_getInfo.data.current_filename)
    let _title = filenamify(o_getInfo.data.many_videos_title)
    let name_ts_file = `${name_time}_${_filename}(${o_getInfo.data.current_videoId}).TS`
    if (o_getInfo.data.has_how_many_videos > 1 || o_getInfo.data.current_filename.startsWith("Part")) {
      name_ts_file = `${name_time}_${_title}_${_filename}(${o_getInfo.data.current_videoId}).TS`
    }
    // name_ts_file = filenamify(name_ts_file);
    // debugger
    cp.exec(`ffmpeg.exe -f concat -i ${path.basename(o_filelist)} -c copy "${name_ts_file}" `, {
      cwd: SAVE_DIR
    }, async (err, stdout, stderr) => {
      if (err) {
        debugger
      }


      debugger
      for (let lf of arr) {
        await fs.unlink(lf);
      }
      await fs.unlink(o_filelist);
      let matched = stderr.match(/video:[0-9]+.*audio:[0-9]+.*subtitle:[0-9]+/g)
      if (matched) {
        console.log(o_getInfo.data.current_filename, o_getInfo.data.current_videoId, "OK");
        if (o_getInfo.data.has_how_many_videos > 1 && page == 1) {
          for (let i = 2; i <= o_getInfo.data.has_how_many_videos; i++) {
            await downloadAllInAcid(`${acid}`, i)
          }
        }
        resolve();
      } else {
        console.log(o_getInfo.data.current_filename, o_getInfo.data.current_videoId, "NEED RE TRY?");
        console.log(stderr);
        let o_retry = await downloadAllInAcid(acid, page);
        resolve();
      }
    })
    // debugger
  })
}

/**
 * @returns {Promise<{ok:Boolean,local_filepath:String}>}
 * @param {String} ts_link 
 */
function downloadTsSegment(ts_link) {
  return new Promise(resolve => {
    let parsed = url.parse(ts_link);
    let basename = path.basename(parsed.pathname); axios.get(ts_link, {
      responseType: "stream"
    }).then(s => {
      let local_f = path.join(PARTS_DIR, basename);
      let writeStream = fs.createWriteStream(local_f);
      s.data.pipe(writeStream);
      writeStream.on("close", () => {
        return resolve({
          ok: true,
          local_filepath: local_f
        })
      });
      writeStream.on("error", async err => {
        return resolve({
          ok: false,
          local_filepath: ""
        })
        console.log(err, ts_link);
        let o_retry = await downloadTsSegment(ts_link);
        resolve(o_retry)
      })
    }).catch(async axerr => {
      return resolve({
        ok: false,
        local_filepath: ""
      })
      console.log(axerr, ts_link);
      let o_retry = await downloadTsSegment(ts_link);
      resolve(o_retry)
    })

    // debugger
  })
}

/**
 * @returns {Promise<String>}
 * @param {String[]} file_fullpaths 
 */
async function createFileList(file_fullpaths) {
  let content = file_fullpaths.map(p => `file '${path.relative(SAVE_DIR, p)}'`.replace(/\\/g, "/")).join("\n");
  let filelist_path = path.join(SAVE_DIR, `file-list-${Math.random()}.txt`);
  let opened = await fs.open(filelist_path, "w");

  await fs.write(opened, content, {
    // flag
  });
  await fs.close(opened);
  // await new Promise(r => setTimeout(r, 1000))
  return filelist_path;
}


// downloadAllInAcid("ac16006738")

/**
 * @returns {Promise<{acids:String[]}>}
 * @param {Number} user_id 4075269 
 * @param {Number} page 
 */
function getVideosOf(user_id, page = 1) {
  return new Promise(resolve => {
    axios.get(`https://www.acfun.cn/u/${user_id}`, {
      params: {
        quickViewId: 'ac-space-video-list',
        reqID: 7,
        ajaxpipe: 1,
        type: 'video',
        order: 'newest',
        page: page,
        pageSize: 20,
        t: Date.now()
      }
    }).then(axresp => {
      // debugger
      let str = axresp.data;
      str = str.replace('/*<!-- fetch-stream -->*/', '');

      let obj = JSON.parse(str);
      let htmlstr = obj.html;
      let matched = htmlstr.match(/"\/v\/(ac[0-9]+)"/g);
      let acids = matched.map(e => e.replace(/"|\/v\//g, ""))
      return resolve({
        acids: acids
      })
      debugger
    })
  })
}

(async function doit() {
  // await downloadAllInAcid('ac4500863', 2);
  // debugger
  for (let i = 61; i <= 65; i++) {
    let o_list = await getVideosOf(4075269, i);
    let tasks = o_list.acids.map(acid => async cb => {
      await downloadAllInAcid(acid);
      cb();
    });
    await new Promise(async resolveXXEDH => {
      runParallel(tasks, 6, () => {
        resolveXXEDH();
      })
    })
  }
})();